package blockQueue;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 74646
 * Date: 2022-05-15
 * Time: 20:47
 */

import com.sun.deploy.uitoolkit.impl.awt.AWTDragHelper;

/**
 * 保证多线程环境下，调用 put 和 take
 * put 和 take 里面的每一行代码都是在操作公共变量，所以直接给整个方法加锁
 * 加上 synchronized  已经是线程安全的了
 * 实现阻塞效果
 * 使用 wait 和 notify 机制
 *      对于 put ：阻塞条件就是队列为满
 *      对于 take：阻塞条件就是队列为空
 *  这两个方法中的唤醒都是彼此唤醒对方
 *
 *  当前代码中，put 和 take 两种操作不会同时 wait （等待条件是截然不同的）
 */
class MyBlockingQueue{
    //保存数据
    private int[] data = new int[1000];
    //保存元素个数
    private int size = 0;
    //队头下标
    private int head = 0;
    //队尾下标
    private int tail = 0;

    //锁对象
    private Object locker = new Object();

    //入队
    public void put(int value){
      synchronized (locker){
          //判断队列是否已满
          if(size==data.length){
              //队列满了，暂时先直接返回
              //return;
              try {
                  /**
                   * 针对哪个对象加锁就使用哪个对象 wait
                   * 如果针对 this 加锁，就 this.wait
                   * 否则 wait 进行解锁时就会抛出异常(锁状态不对)
                   * put 中的 wait 要由 take 唤醒
                   * 只要 take 成功了一个元素，队列就不满了，就可以进行唤醒
                   */
                  locker.wait();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
          //将新的元素放入tail位置
          data[tail] = value;
          tail++;
          //处理 tail 到达末尾的情况
          if(tail>=data.length){
              tail = 0;
          }
          //另一种写法
          // tail  = tail%data.length;
          /**
           * 第一种写法比较好一些
           * 1）第二种写法不直观，可读性比较差
           * 2）% 对于计算机来说开销要更大一些
           */
          size++; //元素入队以后要保证元素个数
          //如果入队列成功，则队列非空，于是就唤醒take中的阻塞等待
          locker.notify();
      }
    }
    //出队
    public Integer take(){
      synchronized (locker){
          //判断队列是否为空
          if(size==0){
              //如果队列为空，则返回一个非法值
              //return null;
              try {
                  /**
                   * 对于 take 中的等待，条件是队列为空，
                   * 此处的等待需要 put 来唤醒
                   * 队列不为空也就是 put 成功后就会唤醒
                   */
                  locker.wait();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
          //取出队头（head位置）元素
          int ret = data[head];
          head++;
          if(head>= data.length){
              head=0;
          }
          size--;
          // take 成功后就唤醒 put 中的等待
          locker.notify();
          return ret;
      }
    }
}

public class Demo2 {
   private static MyBlockingQueue queue = new MyBlockingQueue();
    public static void main(String[] args) {
        //实现一个简单的生产者消费者模型
        Thread producer = new Thread(()->{
            int num = 0;
            while (true){
                System.out.println("生产了："+num);
                queue.put(num);
                num++;
//                try {
//                    // 当生产者生产的慢一些，消费者就要跟着生产者的步伐走
//                    Thread.sleep(500);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            }
        });
        producer.start();

        Thread customer = new Thread(()->{
            while (true){
                int num = queue.take();
                System.out.println("消费了："+num);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        customer.start();

        //验证队列是否正确
//        MyBlockingQueue queue = new MyBlockingQueue();
//        queue.put(1);
//        queue.put(2);
//        queue.put(3);
//        queue.put(4);
//        int ret = 0;
//        ret = queue.take();
//        System.out.println(ret);
//        ret = queue.take();
//        System.out.println(ret);
//        ret = queue.take();
//        System.out.println(ret);
//        ret = queue.take();
//        System.out.println(ret);
    }
}
